/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *  
 *    http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License. 
 *  
 */
package org.apache.directory.studio.proxy.handlers;


import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.future.SearchFuture;
import org.apache.directory.ldap.client.api.message.AbstractResponseWithResult;
import org.apache.directory.ldap.client.api.message.LdapResult;
import org.apache.directory.ldap.client.api.message.SearchRequest;
import org.apache.directory.ldap.client.api.message.SearchResponse;
import org.apache.directory.ldap.client.api.message.SearchResultDone;
import org.apache.directory.ldap.client.api.message.SearchResultEntry;
import org.apache.directory.ldap.client.api.message.SearchResultReference;
import org.apache.directory.shared.ldap.cursor.Cursor;
import org.apache.directory.shared.ldap.exception.LdapInvalidDnException;
import org.apache.directory.shared.ldap.filter.ExprNode;
import org.apache.directory.shared.ldap.message.BindResponseImpl;
import org.apache.directory.shared.ldap.message.ReferralImpl;
import org.apache.directory.shared.ldap.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.message.SearchResponseDoneImpl;
import org.apache.directory.shared.ldap.message.SearchResponseEntryImpl;
import org.apache.directory.shared.ldap.message.SearchResponseReferenceImpl;
import org.apache.directory.shared.ldap.message.internal.InternalBindResponse;
import org.apache.directory.shared.ldap.message.internal.InternalLdapResult;
import org.apache.directory.shared.ldap.message.internal.InternalReferral;
import org.apache.directory.shared.ldap.message.internal.InternalSearchRequest;
import org.apache.directory.shared.ldap.message.internal.InternalSearchResponseDone;
import org.apache.directory.shared.ldap.message.internal.InternalSearchResponseEntry;
import org.apache.directory.shared.ldap.message.internal.InternalSearchResponseReference;
import org.apache.directory.shared.ldap.name.DN;
import org.apache.directory.studio.proxy.*;
import org.apache.mina.core.session.IoSession;
import org.dom4j.Document;
import org.dom4j.Element;


/**
 * Request handler for a search request.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class SearchHandler extends AbstractMessageHandler<InternalSearchRequest>
{
    /* (non-Javadoc)
     * @see org.apache.directory.studio.proxy.handlers.AbstractMessageHandler#handleMessage(org.apache.mina.core.session.IoSession, java.lang.Object)
     */
    public void handleMessage( IoSession session, InternalSearchRequest message ) throws Exception
    {

        super.handleMessage( session, message );

        //Get file/document for session for session
        Document document = ProxyIO.getDocumentForSession( session );
        //Serializing Search request
        ProxyIO.writeSearchRequest( message, document, session );

        LdapConnection connection = LdapConnectionStore.getConnection( session );

        // Forwarding the Search Request
        SearchRequest searchRequest = new SearchRequest();
        searchRequest.setMessageId( message.getMessageId() );
        searchRequest.setBaseDn( message.getBase().getName() );
        searchRequest.setDerefAliases( message.getDerefAliases() );
        searchRequest.setFilter( message.getFilter().toString() );
        List<String> attributes = message.getAttributes();
        for ( String attr : attributes )
        {
            searchRequest.addAttributes( attr );
        }
        searchRequest.setScope( message.getScope() );
        searchRequest.setSizeLimit( message.getSizeLimit() );
        searchRequest.setTimeLimit( message.getTimeLimit() );
        searchRequest.setTypesOnly( message.getTypesOnly() );

        SearchFuture searchFuture = connection.searchAsync( searchRequest );
        SearchResponse searchResponse = null;
        do
        {
            searchResponse = ( SearchResponse ) searchFuture.get( 1000, TimeUnit.MILLISECONDS );

            if ( searchResponse instanceof SearchResultEntry )
            {
                forwardSearchResponseEntry( session, document, searchResponse );
            }

            if ( searchResponse instanceof SearchResultReference )
            {
                forwardSearchResponseReference( session, document, searchResponse );
            }

            if ( searchResponse instanceof SearchResultDone )
            {
                forwardSearchRespnseDone( session, document, searchResponse );
            }

        }
        while ( !( searchResponse instanceof SearchResultDone ) );

    }

    /**
     * Sends back the SearchRespnseDone returned from server to the client
     *
     * @param session
     * @param document
     * @param searchResponse
     * @throws LdapInvalidDnException
     * @throws IOException
     * @throws Exception
     */
    private void forwardSearchRespnseDone( IoSession session, Document document, SearchResponse searchResponse )
        throws LdapInvalidDnException, IOException, Exception
    {
        InternalSearchResponseDone response = new SearchResponseDoneImpl( searchResponse.getMessageId() );

        LdapResult inResult = ( ( SearchResultDone ) searchResponse ).getLdapResult();
        InternalLdapResult outResult = response.getLdapResult();

        forwardLDAPResult( inResult, outResult );

        //Serializing Search response
        ProxyIO.writeSearchResponseDone( response, document, session );

        // Writing the response
        writeResponse( response.toString() );
        session.write( response );
    }

    /**
     * Sends back the SearchResponseReference returned from server to the client
     *
     * @param session
     * @param document
     * @param searchResponse
     * @throws IOException
     * @throws Exception
     */
    private void forwardSearchResponseReference( IoSession session, Document document, SearchResponse searchResponse )
        throws IOException, Exception
    {
        InternalSearchResponseReference response = new SearchResponseReferenceImpl( searchResponse
            .getMessageId() );
        InternalReferral ref = new ReferralImpl();

        Collection<String> ldapUrls = ( Collection<String> ) ( ( SearchResultReference ) searchResponse )
            .getReferrals();
        for ( String url : ldapUrls )
        {
            ref.addLdapUrl( url );
        }

        response.setReferral( ref );

        //Serializing Search response
        ProxyIO.writeSearchResponseReference( response, document, session );

        // Writing the response
        writeResponse( response.toString() );
        session.write( response );
    }

    /**
     * Sends back the SearchRespnseEntry returned from server to the client 
     * @param session
     * @param document
     * @param searchResponse
     * @throws IOException
     * @throws Exception
     */
    private void forwardSearchResponseEntry( IoSession session, Document document, SearchResponse searchResponse )
        throws IOException, Exception
    {
        InternalSearchResponseEntry response = new SearchResponseEntryImpl( searchResponse.getMessageId() );
        SearchResultEntry searchResEnt = ( SearchResultEntry ) searchResponse;
        response.setObjectName( searchResEnt.getObjectName() );
        response.setEntry( searchResEnt.getEntry() );

        //Serializing Search response
        ProxyIO.writeSearchResponseEntry( response, document, session );

        // Writing the response
        writeResponse( response.toString() );
        session.write( response );
    }

}
